﻿//////////////////////////////////////////////////
//                  Spell.cs                    //
//         Part of RebornCo by kaihaider        //
//////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
using Buddy.Coroutines;
using ff14bot;
using ff14bot.Enums;
using ff14bot.Helpers;
using ff14bot.Managers;
using ff14bot.Objects;

namespace RebornCo.Helpers
{
    public static class Spell
    {
        public static bool RotationSwap, Opener;
        public static string LogFilter, NoneGcdCache, NoneGcdLast;
        public static GameObject NoneGcdCacheTarget, NoneGcdLastTarget;
        public static double QueueLogFilter;
        public static BagSlot Potion;
        public static SpellData GetSpellData(string spell)
        {
            SpellData data;
            Actionmanager.CurrentActions.TryGetValue(spell, out data);
            return data;
        }
        #region PullCast
        
        public static async Task<bool> PullCast(string name)
        {
            return await PullCast(name, Core.Player.CurrentTarget, true);
        }
        public static async Task<bool> PullCast(string name, bool cond)
        {
            return await PullCast(name, Core.Player.CurrentTarget, cond);
        }
        public static async Task<bool> PullCast(string name, GameObject o)
        {
            return await PullCast(name, o, true);
        }
        public static async Task<bool> PullCast(string name, GameObject o, bool cond)
        {
            if (!RebornCoRoutine.WindowSettings.Movement || Core.Player.IsCasting || !cond) return false;
            o = o ?? Core.Player.CurrentTarget;
            if (o == null) { return false; }
            var ck = Actionmanager.InSpellInRangeLOS(name, o);
            if (ck == SpellRangeCheck.ErrorNotInLineOfSight
                    || ck == SpellRangeCheck.ErrorNotInRange && !(MovementManager.IsMoving && Movement.IsFacingMovement(Core.Player, o)))
            {
                Movement.MoveTo(o);
                return true;
            }
            //if (ck == SpellRangeCheck.ErrorNotInFront) return await Movement.CheckFace(o);
            return await Cast(name, o);
        }

        #endregion
        #region combo
        public static bool ComboCountCheck;
        public static Combo LastCombo;
        public static Combo CachedCombo;
        public static TimeSpan ComboCountdown;
        public static Dictionary<uint, Combo> ComboSpells = new Dictionary<uint, Combo>
        {
            {75, Combo.TrueThrust},
            {78, Combo.VorpalThrust},
            {79, Combo.HeavyThrust},
            {81, Combo.ImpulseDrive},
            {87, Combo.Disembowel},
            {2240, Combo.SpinningEdge},
            {2242, Combo.GustSlash},
            {84, Combo.FullThrust},
            {88, Combo.ChaosThrust},
            {89, Combo.RingofThorns},
            {2251, Combo.DancingEdge},
            {2255, Combo.AeolianEdge},
            {2257, Combo.ShadowFang},
            {31, Combo.HeavySwing},
            {35, Combo.SkullSunder},
            {37, Combo.Maim},
            {42, Combo.StormsPath},
            {45, Combo.StormsEye},
            {47, Combo.ButchersBlock},
            {9, Combo.FastBlade},
            {11, Combo.SavageBlade},
            {15, Combo.RiotBlade},
            {21, Combo.RageofHalone},
            {154, Combo.Blizzard3},
        };

        public static void SyncCombos()
        {
            if (!ComboCountCheck) return;
            if (ComboCountdown > DateTime.UtcNow.TimeOfDay)
            {
                if (!Gcd || CachedCombo == Combo.Flushed) return;
                LastCombo = CachedCombo;
                CachedCombo = Combo.Flushed;
            }
            else
            {
                CachedCombo = Combo.Flushed;
                if (ComboSpells.ContainsKey(Actionmanager.LastSpellId))
                    LastCombo = ComboSpells[Actionmanager.LastSpellId];
                else
                {
                    ComboCountCheck = false;
                    if ((LastCombo | Combo.Finished) != Combo.Finished)
                        Logging.Write(Colors.OrangeRed, "[RebornCo] Clearing " + LastCombo + " because " + Actionmanager.LastSpell);
                    LastCombo = Combo.Finished;

                }
            }
        }
#endregion
        #region gcd

        public static TimeSpan GcdTimeSpan;
        public static double GcdTime;
        public static bool Gcd, UsedNoneGcd;

        public static void GcdPulse()
        {
            if (Core.Player.CurrentTarget != null && Movement.IsInSafeRange(Core.Player.CurrentTarget) && Core.Player.CurrentTarget.InLineOfSight())
            {
                if (!Extensions.DoubleCastPreventionDict.Contains(null, "gcd")) SetGcd();
                else GcdTime = (GcdTimeSpan - DateTime.UtcNow.TimeOfDay).TotalMilliseconds;
            }
            else GcdTime = 0;
            Gcd = GcdTime > RebornCoRoutine.WindowSettings.SpellQueue;
            if ((!UsedNoneGcd && NoneGcdCache == null) || !(GcdTime < RebornCoRoutine.WindowSettings.NoneGcdWindowEnd))
                return;
            NoneGcdLastTarget = NoneGcdCacheTarget;
            NoneGcdCacheTarget = null;
            NoneGcdLast = NoneGcdCache;
            NoneGcdCache = null;
            UsedNoneGcd = false;
        }
        public static bool InGcd()
        {
            return GcdTime > RebornCoRoutine.WindowSettings.SpellQueue;
        }
        public static bool NotCasting()
        {
            return !Core.Player.IsCasting;
        }
        public static bool InvalidOrOutGcdCon()
        {
            return (Core.Player.CurrentTarget as BattleCharacter) == null || (Core.Player.CurrentTarget as BattleCharacter).IsDead || GcdTime < 480;
        }
        public static void SetGcd()
        {
            switch (Core.Player.CurrentJob)
            {
                case ClassJobType.Ninja:
                case ClassJobType.Rogue:
                    GcdTime = GcdMs("Spinning Edge");
                    break;
                case ClassJobType.Dragoon:
                case ClassJobType.Lancer:
                    GcdTime = GcdMs("True Thrust");
                    break;
                case ClassJobType.Monk:
                case ClassJobType.Pugilist:
                    GcdTime = GcdMs("Bootshine");
                    break;
                case ClassJobType.Conjurer:
                case ClassJobType.WhiteMage:
                    GcdTime = GcdMs("Stone");
                    break;
                case ClassJobType.Marauder:
                case ClassJobType.Warrior:
                    GcdTime = GcdMs("Heavy Swing");
                    break;
                case ClassJobType.Gladiator:
                case ClassJobType.Paladin:
                    GcdTime = GcdMs("Fast Blade");
                    break;
                case ClassJobType.Archer:
                case ClassJobType.Bard:
                    GcdTime = GcdMs("Heavy Shot");
                    break;
                case ClassJobType.Thaumaturge:
                case ClassJobType.BlackMage:
                    GcdTime = GcdMs("Blizzard");
                    break;
                case ClassJobType.Arcanist:
                case ClassJobType.Summoner:
                case ClassJobType.Scholar:
                    GcdTime = GcdMs("Ruin");
                    break;
            }
            if (!InGcd()) return;
            GcdTimeSpan = DateTime.UtcNow.TimeOfDay + TimeSpan.FromMilliseconds(GcdTime);
            Extensions.UpdateDoubleCastDictSpecific("gcd", null, (GcdTime - RebornCoRoutine.WindowSettings.SpellQueue)/1000);
        }
        public static double GcdMs(string spell)
        {
            var data = GetSpellData(spell);
            if (data == null) return 0;
            var cooldown = data.Cooldown.TotalMilliseconds;
            return (Math.Abs(cooldown - data.AdjustedCooldown.TotalMilliseconds) < 50
                || Math.Abs(cooldown - data.BaseCooldown.TotalMilliseconds) < 50) ? 0 : cooldown;
        }
        #endregion
        #region Cast - by name
        public static bool CanCast(SpellData data, GameObject o)
        {
            if (o == null || data == null) return false;
            var ck = Actionmanager.InSpellInRangeLOS(data, o);
            if (ck == SpellRangeCheck.ErrorNotInRange || ck == SpellRangeCheck.ErrorNotInLineOfSight)
            {
                Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + data.Name + " Failed: Range/LoS");
                return false;
            }
            if (ck == SpellRangeCheck.ErrorNotInFront)
            {
                if(RebornCoRoutine.WindowSettings.Facing)
                {
                    Logging.Write(Colors.Red, "[RebornCo] Attempted to cast while not facing!");
                    Movement.Face(o);
                }
            }
            var cooldown = data.Cooldown.TotalMilliseconds;
            if (cooldown < RebornCoRoutine.WindowSettings.SpellQueue ||
                Math.Abs(cooldown - data.AdjustedCooldown.TotalMilliseconds) < 50
                || Math.Abs(cooldown - data.BaseCooldown.TotalMilliseconds) < 50)
            {
                if (RebornCoRoutine.WindowSettings.Dodge && Movement.Avoiding && cooldown < 10 && Movement.AvoidTill > DateTime.UtcNow.TimeOfDay && !Movement.IsFacingMovement(Core.Player, Movement.AvoidTo)) Movement.StopMove();
                return true;
            }
            if (cooldown > 2500) Extensions.UpdateDoubleCastDictSpecific(data.Name, null, (cooldown - RebornCoRoutine.WindowSettings.SpellQueue)/1000);
            if (RebornCoRoutine.WindowSettings.Debug)
                Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + data.Name + " CanCast Failed: " + cooldown);
            return false;
        }

        public static async Task<bool> CastLock(string name)
        {
            return await Cast(name, Core.Player.CurrentTarget, true, false,true);
        }

        public static async Task<bool> CastLock(string name, GameObject o)
        {
            return await Cast(name, o, true, false,true);
        }

        public static async Task<bool> CastLock(string name, bool cond)
        {
            return await Cast(name, Core.Player.CurrentTarget, cond, false, true);
        }
        public static async Task<bool> CastLock(string name, GameObject o, bool cond)
        {
            return await Cast(name, o, cond, false,true);
        }
        public static async Task<bool> Cast(string name)
        {
            return await Cast(name, Core.Player.CurrentTarget, true);
        }

        public static async Task<bool> Cast(string name, GameObject o)
        {
            return await Cast(name, o, true);
        }

        public static async Task<bool> Cast(string name, bool cond)
        {
            return await Cast(name, Core.Player.CurrentTarget, cond);
        }
#pragma warning disable 1998
        public static async Task<bool> Cast(string name, GameObject o, bool cond, bool ignoreCanCast = false, bool lockDoubleCast = false, bool falseOnFailedDoAction = false)
#pragma warning restore 1998
        {
            SpellData data;
            if (Actionmanager.CurrentActions.TryGetValue(name, out data))
            {
                if (RebornCoRoutine.WindowSettings.Debug && data.Id == 125)
                {
                    if(o!=null) 
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) Cast Raise Attempt: " + o.ObjectId);
                    else 
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) Cast Raise Attempt on null!");
                }

                o = o ?? Core.Player.CurrentTarget;
                if ((lockDoubleCast && Extensions.DoubleCastPreventionDict.Contains(o, name) || Extensions.DoubleCastPreventionDict.Contains(null, name))
                    || (o as Character) != null && (o as Character).IsDead && o.Type != GameObjectType.Pc
                    || !cond || !ignoreCanCast && !CanCast(data, o)) return false;
                var castTime = data.AdjustedCastTime.TotalSeconds > 0;
                if (castTime && MovementManager.IsMoving && !Core.Player.HasAura("Swiftcast"))
                {
                    if (!RebornCoRoutine.WindowSettings.AttemptCastWhileMoving)
                        return false;
                    Movement.StopMove();
                }
                if (RebornCoRoutine.WindowSettings.Debug && RebornCoRoutine.WindowSettings.WriteSpellQueue &&
                    GcdTime < 500
                    && (!name.Equals(LogFilter) || QueueLogFilter < GcdTime))
                {
                    QueueLogFilter = GcdTime;
                    Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + GcdTime + " Spell Queue");
                }
                else if (QueueLogFilter > GcdTime) QueueLogFilter = GcdTime;
                RebornCoRoutine.ShouldPulse = true;
                if (ComboSpells.ContainsKey(data.Id))
                {
                    if (LastCombo != ComboSpells[data.Id])
                    {
                        if (castTime)
                            ComboCountdown = DateTime.UtcNow.TimeOfDay +
                                             TimeSpan.FromSeconds(GcdTime/1000 + 1 + data.AdjustedCastTime.TotalSeconds);
                        else
                            ComboCountdown = DateTime.UtcNow.TimeOfDay +
                                             TimeSpan.FromSeconds(GcdTime/1000 + 1);
                        ComboCountCheck = true;
                        if (CachedCombo == Combo.Flushed)
                            CachedCombo = ComboSpells[data.Id];
                    }
                }
               // else if(!Gcd) {}  
                if (!Actionmanager.DoAction(name, o))
                {
                  //  if (RebornCoRoutine.WindowSettings.Debug) Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " DoAction Failed");
                    if (falseOnFailedDoAction)
                    {
                        LogFilter = name;
                        return false;
                    }
                }
                else if (lockDoubleCast)
                {
                    if (RebornCoRoutine.WindowSettings.Debug)
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " DoAction Success, applying double cast prevention: " + (2.5 + data.AdjustedCastTime.TotalSeconds));
                    if (castTime) Extensions.UpdateDoubleCastDictSpecific(name, o, 2.5 + data.AdjustedCastTime.TotalSeconds);
                    else 
                        Extensions.UpdateDoubleCastDictSpecific(name, o);
                }
                if (!name.Equals(LogFilter))
                {
                    Logging.Write(Colors.Orchid, "[RebornCo] Casting " + name);
                    LogFilter = name;
                }
                return true;
            }
            return false;
        }

#pragma warning disable 1998
        public static async Task<bool> CastLocation(string name, GameObject o, bool cond, bool ignoreCanCast = false, bool lockDoubleCast = false)
#pragma warning restore 1998
        {
                SpellData data;
                if (Actionmanager.CurrentActions.TryGetValue(name, out data))
                {
                    o = o ?? Core.Player.CurrentTarget;
                    var loc = o.Location;
                    if ((Extensions.DoubleCastPreventionDict.Contains(o, name) || Extensions.DoubleCastPreventionDict.Contains(null, name))
                        || (o as Character) != null && (o as Character).IsDead && o.Type != GameObjectType.Pc
                        || !cond || !ignoreCanCast && !CanCast(data, o)) return false;
                    var castTime = data.AdjustedCastTime.TotalSeconds > 0;
                    if (castTime && MovementManager.IsMoving && !Core.Player.HasAura("Swiftcast"))
                    {
                        if (!RebornCoRoutine.WindowSettings.AttemptCastWhileMoving)
                            return false; //BotManager.Current.IsAutonomous;
                        Movement.StopMove();
                    }

                    RebornCoRoutine.ShouldPulse = true;
                    if (Actionmanager.DoActionLocation(name, loc))
                    {
                        Logging.Write(Colors.OrangeRed, "[RebornCo] Casting {0} at {1}", name, loc);
                        if (lockDoubleCast)
                        {
                            if (RebornCoRoutine.WindowSettings.Debug)
                                Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " DoAction Success, applying double cast prevention.");
                            Extensions.UpdateDoubleCastDictSpecific(name, o);
                        }
                    }
                    else if (RebornCoRoutine.WindowSettings.Debug)
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " DoAction Failed");
                    return true;
                }
                return false;
        }

        public static async Task<bool> ConfirmedCast(string name, GameObject o, bool cond = true, bool lockDoubleCast = false,bool spellData = false)
        {
            SpellData data;
            if (Actionmanager.CurrentActions.TryGetValue(name, out data))
            {

                if (RebornCoRoutine.WindowSettings.Debug && data.Id == 125)
                {
                    if (o != null)
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) ConfirmedCast Raise Attempt: " + o.ObjectId);
                    else
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) ConfirmedCast Raise Attempt on null!");
                }

                o = o ?? Core.Player.CurrentTarget;
                if (Extensions.DoubleCastPreventionDict.Contains(o, name) || Extensions.DoubleCastPreventionDict.Contains(null, name)
                    || (o as Character) != null && (o as Character).IsDead && o.Type != GameObjectType.Pc
                    || !cond || !CanCast(data, o)) return false;
                var castTime = data.AdjustedCastTime.TotalSeconds > 0;
                if (castTime && MovementManager.IsMoving && !Core.Player.HasAura("Swiftcast"))
                {
                    if (!RebornCoRoutine.WindowSettings.AttemptCastWhileMoving)
                        return false;
                    Movement.StopMove();
                }
                RebornCoRoutine.ShouldPulse = true;
                if (!name.Equals(LogFilter))
                {
                    Logging.Write(Colors.Orchid, "[RebornCo] Casting " + name);
                    LogFilter = name;
                }

                if (RebornCoRoutine.WindowSettings.Debug && RebornCoRoutine.WindowSettings.WriteSpellQueue && GcdTime < 500)
                    Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + GcdTime + " Looped DoAction Start");
                if (spellData)
                {
                    if (!await Coroutine.Wait(1200 + RebornCoRoutine.WindowSettings.SpellQueue, () => 
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
                        (Actionmanager.DoAction(name, o) || true) && Core.Player.IsCasting && Core.Player.CastingSpellId == data.Id))
                    {
                        if (RebornCoRoutine.WindowSettings.Debug)
                            Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " Looped DoAction Failed");
                        return false;
                    }
                    if (lockDoubleCast)
                    { 
                    if (castTime) Extensions.UpdateDoubleCastDictSpecific(name, o, 2.5 + data.AdjustedCastTime.TotalSeconds);
                    else
                        Extensions.UpdateDoubleCastDictSpecific(name, o);
                    if (RebornCoRoutine.WindowSettings.Debug)
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + data.Name + " Applying Cast Prevention: " + (data.Cooldown.TotalMilliseconds - RebornCoRoutine.WindowSettings.SpellQueue));
 
                    }
                }
                else if (lockDoubleCast)
                {
                    if (!await Coroutine.Wait(500 + RebornCoRoutine.WindowSettings.SpellQueue, () => Actionmanager.DoAction(name, o)))
                    {
                        if (RebornCoRoutine.WindowSettings.Debug)
                            Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " Looped DoAction Failed");
                        return false;
                    } 
                    if (castTime) Extensions.UpdateDoubleCastDictSpecific(name, o, 2.5 + data.AdjustedCastTime.TotalSeconds);
                    else
                        Extensions.UpdateDoubleCastDictSpecific(name, o);
                    if (RebornCoRoutine.WindowSettings.Debug)
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + data.Name + " Applying Cast Prevention: " + (data.Cooldown.TotalMilliseconds - RebornCoRoutine.WindowSettings.SpellQueue));
                }
                else
                {
                    if (!await Coroutine.Wait(500 + RebornCoRoutine.WindowSettings.SpellQueue, () => Actionmanager.DoAction(name, o) && data.Cooldown.TotalMilliseconds > 500 + RebornCoRoutine.WindowSettings.SpellQueue || data.Cooldown.TotalMilliseconds > 500 + RebornCoRoutine.WindowSettings.SpellQueue))
                    {
                        if (RebornCoRoutine.WindowSettings.Debug)
                            Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + name + " Looped DoAction Failed");
                        return false;
                    }
                    if (castTime) Extensions.UpdateDoubleCastDictSpecific(name, o, (data.Cooldown.TotalMilliseconds - RebornCoRoutine.WindowSettings.SpellQueue) / 1000 + data.AdjustedCastTime.TotalSeconds);
                    else
                    Extensions.UpdateDoubleCastDictSpecific(data.Name, null, (data.Cooldown.TotalMilliseconds - RebornCoRoutine.WindowSettings.SpellQueue) / 1000);
                    if (RebornCoRoutine.WindowSettings.Debug)
                        Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + data.Name + " Applying Cast Prevention: " + (data.Cooldown.TotalMilliseconds - RebornCoRoutine.WindowSettings.SpellQueue));
                }
                if (RebornCoRoutine.WindowSettings.Debug && RebornCoRoutine.WindowSettings.WriteSpellQueue && GcdTime < 500)
                    Logging.WriteToFileSync(LogLevel.Normal, "(RebornCo) " + GcdTime + " Looped Spell Queue");
                return true;
            }
            return false;
        }

        public static async Task<bool> CastHeal(string name, GameObject o, bool castLock)
        {
            if (await Cast(name, o, true, false, false, castLock))
            {
                return true;
            }
            return false;
        }
        public static async Task<bool> BuffParty(string name, bool cond = true, bool castAll = true)
        {
            if (PartyManager.IsInParty)
            {
                SpellData data;
                if (!Actionmanager.CurrentActions.TryGetValue(name, out data)) return false;
                if (!cond) return false;
                var target = PartyManager.VisibleMembers.FirstOrDefault(
        unit => unit.IsValid && unit.GameObject.Type == GameObjectType.Pc && (unit.GameObject as Character) != null &&  (unit.GameObject as Character).IsAlive && !unit.GameObject.HasAura(name));
                GameObject o;
                if (target != null) o = target.GameObject;
                else if (!Core.Player.HasAura(name)) o = Core.Player;
                else return false;
                if (Extensions.DoubleCastPreventionDict.Contains(castAll ? Core.Player : o, name)) return false;
                if (!CanCast(data, castAll ? Core.Player : o)) return false;
                var castTime = data.AdjustedCastTime.TotalSeconds > 0;
                if (castTime)
                {
                    if (!RebornCoRoutine.WindowSettings.AttemptCastWhileMoving && MovementManager.IsMoving)
                        return false;
                    Movement.StopMove();
                }
                if (!Actionmanager.DoAction(name, castAll ? Core.Player : o)) return false;
                if (!name.Equals(LogFilter))
                {
                    Logging.Write(Colors.Orchid, "[RebornCo] Buffing " + name);
                    LogFilter = name;
                }
                Extensions.UpdateDoubleCastDictSpecific(name, castAll ? Core.Player : o, 12);
                return true;
            }

            if (!await Cast(name, Core.Player, cond && !Core.Player.HasAura(name), false, true, true)) return false;
            Extensions.UpdateDoubleCastDictSpecific(name, Core.Player, 12);
            return true;
        }

#pragma warning disable 1998
        public static async Task<bool> NoneGcdCast(string name, GameObject o, bool cond, bool force = false)
#pragma warning restore 1998
        {
            if (o == null || Extensions.DoubleCastPreventionDict.Contains(null, name)
                || !force && (!(string.IsNullOrEmpty(NoneGcdCache) || NoneGcdCache.Equals(name)) || !cond)) return false;
            NoneGcdCache = name;
            NoneGcdCacheTarget = o;
            UsedNoneGcd = true;
            await Cast(name, o, true);
            return true;
        }

        #endregion

    }
}
